/*
*Copyright 2021-2023 NERCIS
*
*Licensed under the Apache License, Version 2.0 (the "License");
*you may not use this file except in compliance with the License.
*You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*Unless required by applicable law or agreed to in writing, software
*distributed under the License is distributed on an "AS IS" BASIS,
*WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*See the License for the specific language governing permissions and
*limitations under the License.
*/

package cn.ac.nercis.pes.service.project.imported;

import cn.ac.nercis.pes.common.constant.common.BaseConstants;
import cn.ac.nercis.pes.common.constant.knowledge.AssetClassification;
import cn.ac.nercis.pes.model.dto.common.Result;
import cn.ac.nercis.pes.model.dto.project.ImportConflictDTO;
import cn.ac.nercis.pes.model.standard.asset.AssetAttributeSDTO;
import cn.ac.nercis.pes.model.standard.asset.AssetSDTO;
import cn.ac.nercis.pes.repository.dal.evaluation.AssetRepository;
import cn.ac.nercis.pes.repository.mapper.standard.SystemImportMapper;
import cn.ac.nercis.pes.repository.model.evaluation.AssetAttributeDO;
import cn.ac.nercis.pes.repository.model.evaluation.AssetDO;
import cn.ac.nercis.pes.service.exception.ImportAssetFailException;
import cn.ac.nercis.pes.service.utils.CheckFieldUtils;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 系统资产数据导入服务
 *
 * @author zpy
 * @version 1.0.0
 */
@Slf4j
@Service
public class SystemAssetImportService extends CommonImportService {
    private final AssetRepository assetRepository;

    @Autowired
    public SystemAssetImportService(AssetRepository assetRepository) {
        this.assetRepository = assetRepository;
    }

    public Result<List<ImportConflictDTO>> checkAssetData(@NonNull String systemId, List<AssetSDTO> assets) {
        List<AssetDO> sourceData = this.assetRepository.findAllBySystemId(systemId);
        if (CollectionUtils.isEmpty(sourceData)) {
            return Result.success();
        }
        var conflictAssetData = assets.stream()
                .flatMap(ec -> this.checkAssetData(ec, assets, sourceData).stream())
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        return Result.success(conflictAssetData);
    }

    public Result<String> importAssetData(@NonNull String systemId, List<AssetSDTO> assets, Boolean cover) {
        try {
            this.importAssetDataByPhyEnv(systemId, assets, cover);
            this.importAssetDataByPhySecurity(systemId, assets, cover);
            this.importAssetDataByPwdProduct(systemId, assets, cover);
            this.importAssetDataByServerStorage(systemId, assets, cover);
            this.importAssetDataByNetworkSecurity(systemId, assets, cover);
            this.importAssetDataByDatabase(systemId, assets, cover);
            this.importAssetDataByKeyBusiness(systemId, assets, cover);
            this.importAssetDataBySubBusiness(systemId, assets, cover);
            this.importAssetDataByKeyData(systemId, assets, cover);
            this.importAssetDataByCommunicationChannel(systemId, assets, cover);
            this.importAssetDataByManageDoc(systemId, assets, cover);
            return Result.success();
        } catch (ImportAssetFailException failException) {
            return Result.failed(failException.getMessage());
        }
    }

    public void importAssetDataByPhyEnv(@NonNull String systemId, List<AssetSDTO> assets, Boolean cover) throws ImportAssetFailException {
        List<AssetSDTO> importAssets = assets.stream()
                .filter(a -> StringUtils.equals(a.getClassId(), AssetClassification.PHY_ENV.getId()))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(importAssets)) {
            return;
        }
        List<AssetDO> sourceData = this.assetRepository.findAllBySystemIdAndClassId(systemId, AssetClassification.PHY_ENV.getId());
        Result<String> result;
        for (AssetSDTO item : importAssets) {
            AssetDO entity = findAssetByPhyEnv(SystemImportMapper.INSTANCE.toPhyEnvDTO(item), sourceData);
            if (Objects.isNull(entity)) {
                result = createAssetData(systemId, item);
            } else {
                result = modifyAssetData(entity, item, cover);
            }
            if (!result.isSuccess()) {
                throw new ImportAssetFailException(String.format("导入%s资产[%s]失败", AssetClassification.PHY_ENV.getName(), item.getName()));
            }
        }
    }

    public void importAssetDataByPhySecurity(@NonNull String systemId, List<AssetSDTO> assets, Boolean cover) throws ImportAssetFailException {
        List<AssetSDTO> importAssets = assets.stream()
                .filter(a -> StringUtils.equals(a.getClassId(), AssetClassification.PHY_SECURITY.getId()))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(importAssets)) {
            return;
        }
        List<AssetDO> sourceData = this.assetRepository.findAllBySystemId(systemId);
        Result<String> result;
        for (AssetSDTO item : importAssets) {
            item.setQuoteAssetId(getQuoteAssetId(item, assets, sourceData));
            AssetDO entity = findAssetByPhySecurity(SystemImportMapper.INSTANCE.toPhySecurity(item), sourceData);
            if (Objects.isNull(entity)) {
                result = createAssetData(systemId, item);
            } else {
                result = modifyAssetData(entity, item, cover);
            }
            if (!result.isSuccess()) {
                throw new ImportAssetFailException(String.format("导入%s资产[%s]失败", AssetClassification.PHY_SECURITY.getName(), item.getName()));
            }
        }
    }

    public void importAssetDataByPwdProduct(@NonNull String systemId, List<AssetSDTO> assets, Boolean cover) throws ImportAssetFailException {
        List<AssetSDTO> importAssets = assets.stream()
                .filter(a -> StringUtils.equals(a.getClassId(), AssetClassification.PWD_PRODUCT.getId()))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(importAssets)) {
            return;
        }
        List<AssetDO> sourceData = this.assetRepository.findAllBySystemIdAndClassId(systemId, AssetClassification.PWD_PRODUCT.getId());
        Result<String> result;
        for (AssetSDTO item : importAssets) {
            AssetDO entity = findAssetByPwdProduct(SystemImportMapper.INSTANCE.toPwdProduct(item), sourceData);
            if (Objects.isNull(entity)) {
                result = createAssetData(systemId, item);
            } else {
                result = modifyAssetData(entity, item, cover);
            }
            if (!result.isSuccess()) {
                throw new ImportAssetFailException(String.format("导入%s资产[%s]失败", AssetClassification.PWD_PRODUCT.getName(), item.getName()));
            }
        }
    }

    public void importAssetDataByServerStorage(@NonNull String systemId, List<AssetSDTO> assets, Boolean cover) throws ImportAssetFailException {
        List<AssetSDTO> importAssets = assets.stream()
                .filter(a -> StringUtils.equals(a.getClassId(), AssetClassification.SERVER_STORAGE.getId()))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(importAssets)) {
            return;
        }
        List<AssetDO> sourceData = this.assetRepository.findAllBySystemIdAndClassId(systemId, AssetClassification.SERVER_STORAGE.getId());
        Result<String> result;
        for (AssetSDTO item : importAssets) {
            AssetDO entity = findAssetByServerStorage(SystemImportMapper.INSTANCE.toServerStorage(item), sourceData);
            if (Objects.isNull(entity)) {
                result = createAssetData(systemId, item);
            } else {
                result = modifyAssetData(entity, item, cover);
            }
            if (!result.isSuccess()) {
                throw new ImportAssetFailException(String.format("导入%s资产[%s]失败", AssetClassification.SERVER_STORAGE.getName(), item.getName()));
            }
        }
    }

    public void importAssetDataByNetworkSecurity(@NonNull String systemId, List<AssetSDTO> assets, Boolean cover) throws ImportAssetFailException {
        List<AssetSDTO> importAssets = assets.stream()
                .filter(a -> StringUtils.equals(a.getClassId(), AssetClassification.NETWORK_SECURITY.getId()))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(importAssets)) {
            return;
        }
        List<AssetDO> sourceData = this.assetRepository.findAllBySystemIdAndClassId(systemId, AssetClassification.NETWORK_SECURITY.getId());
        Result<String> result;
        for (AssetSDTO item : importAssets) {
            AssetDO entity = findAssetByNetworkSecurity(SystemImportMapper.INSTANCE.toNetworkSecurity(item), sourceData);
            if (Objects.isNull(entity)) {
                result = createAssetData(systemId, item);
            } else {
                result = modifyAssetData(entity, item, cover);
            }
            if (!result.isSuccess()) {
                throw new ImportAssetFailException(String.format("导入%s资产[%s]失败", AssetClassification.NETWORK_SECURITY.getName(), item.getName()));
            }
        }
    }

    public void importAssetDataByDatabase(@NonNull String systemId, List<AssetSDTO> assets, Boolean cover) throws ImportAssetFailException {
        List<AssetSDTO> importAssets = assets.stream()
                .filter(a -> StringUtils.equals(a.getClassId(), AssetClassification.DATABASE.getId()))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(importAssets)) {
            return;
        }
        List<AssetDO> sourceData = this.assetRepository.findAllBySystemId(systemId);
        Result<String> result;
        for (AssetSDTO item : importAssets) {
            item.setQuoteAssetId(getQuoteAssetId(item, assets, sourceData));
            AssetDO entity = findAssetByDatabase(SystemImportMapper.INSTANCE.toDatabase(item), sourceData);
            if (Objects.isNull(entity)) {
                result = createAssetData(systemId, item);
            } else {
                result = modifyAssetData(entity, item, cover);
            }
            if (!result.isSuccess()) {
                throw new ImportAssetFailException(String.format("导入%s资产[%s]失败", AssetClassification.DATABASE.getName(), item.getName()));
            }
        }
    }

    public void importAssetDataByKeyBusiness(@NonNull String systemId, List<AssetSDTO> assets, Boolean cover) throws ImportAssetFailException {
        List<AssetSDTO> importAssets = assets.stream()
                .filter(a -> StringUtils.equals(a.getClassId(), AssetClassification.KEY_BUSINESS.getId()))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(importAssets)) {
            return;
        }
        List<AssetDO> sourceData = this.assetRepository.findAllBySystemId(systemId);
        Result<String> result;
        for (AssetSDTO item : importAssets) {
            item.setQuoteAssetId(getQuoteAssetId(item, assets, sourceData));
            AssetDO entity = findAssetByKeyBusiness(SystemImportMapper.INSTANCE.toKeyBusiness(item), sourceData);
            if (Objects.isNull(entity)) {
                result = createAssetData(systemId, item);
            } else {
                result = modifyAssetData(entity, item, cover);
            }
            if (!result.isSuccess()) {
                throw new ImportAssetFailException(String.format("导入%s资产[%s]失败", AssetClassification.KEY_BUSINESS.getName(), item.getName()));
            }
        }
    }

    public void importAssetDataBySubBusiness(@NonNull String systemId, List<AssetSDTO> assets, Boolean cover) throws ImportAssetFailException {
        List<AssetSDTO> importAssets = assets.stream()
                .filter(a -> StringUtils.equals(a.getClassId(), AssetClassification.SUB_BUSINESS.getId()))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(importAssets)) {
            return;
        }
        List<AssetDO> sourceData = this.assetRepository.findAllBySystemId(systemId);
        Result<String> result;
        for (AssetSDTO item : importAssets) {
            item.setQuoteAssetId(getQuoteAssetId(item, assets, sourceData));
            AssetDO entity = findAssetBySubBusiness(SystemImportMapper.INSTANCE.toSubBusiness(item), sourceData);
            if (Objects.isNull(entity)) {
                result = createAssetData(systemId, item);
            } else {
                result = modifyAssetData(entity, item, cover);
            }
            if (!result.isSuccess()) {
                throw new ImportAssetFailException(String.format("导入%s资产[%s]失败", AssetClassification.SUB_BUSINESS.getName(), item.getName()));
            }
        }
    }

    public void importAssetDataByKeyData(@NonNull String systemId, List<AssetSDTO> assets, Boolean cover) throws ImportAssetFailException {
        List<AssetSDTO> importAssets = assets.stream()
                .filter(a -> StringUtils.equals(a.getClassId(), AssetClassification.KEY_DATA.getId()))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(importAssets)) {
            return;
        }
        List<AssetDO> sourceData = this.assetRepository.findAllBySystemId(systemId);
        Result<String> result;
        for (AssetSDTO item : importAssets) {
            item.setKeyDataAssetId("");
            if (StringUtils.isNotBlank(item.getQuoteAssetId())) {
                item.setQuoteAssetId(getQuoteAssetId(item, importAssets, sourceData));
            }
            AssetDO entity = findAssetByKeyData(SystemImportMapper.INSTANCE.toKeyData(item), sourceData);
            if (Objects.isNull(entity)) {
                result = createAssetData(systemId, item);
            } else {
                result = modifyAssetData(entity, item, cover);
            }
            if (!result.isSuccess()) {
                throw new ImportAssetFailException(String.format("导入重要数据资产[%s]失败", item.getName()));
            }
        }
    }

    public void importAssetDataByCommunicationChannel(@NonNull String systemId, List<AssetSDTO> assets, Boolean cover) throws ImportAssetFailException {
        List<AssetSDTO> importAssets = assets.stream()
                .filter(a -> StringUtils.equals(a.getClassId(), AssetClassification.COMMUNICATION_CHANNEL.getId()))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(importAssets)) {
            return;
        }
        List<AssetDO> sourceData = this.assetRepository.findAllBySystemIdAndClassId(systemId, AssetClassification.COMMUNICATION_CHANNEL.getId());
        Result<String> result;
        for (AssetSDTO item : importAssets) {
            AssetDO entity = findAssetByName(item.getName(), sourceData);
            if (Objects.isNull(entity)) {
                result = createAssetData(systemId, item);
            } else {
                result = modifyAssetData(entity, item, cover);
            }
            if (!result.isSuccess()) {
                throw new ImportAssetFailException(String.format("导入%s资产[%s]失败", AssetClassification.COMMUNICATION_CHANNEL.getName(), item.getName()));
            }
        }
    }

    public void importAssetDataByManageDoc(@NonNull String systemId, List<AssetSDTO> assets, Boolean cover) throws ImportAssetFailException {
        List<AssetSDTO> importAssets = assets.stream()
                .filter(a -> StringUtils.equals(a.getClassId(), AssetClassification.MANAGE_DOC.getId()))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(importAssets)) {
            return;
        }
        List<AssetDO> sourceData = this.assetRepository.findAllBySystemIdAndClassId(systemId, AssetClassification.MANAGE_DOC.getId());
        Result<String> result;
        for (AssetSDTO item : importAssets) {
            AssetDO entity = findAssetByName(item.getName(), sourceData);
            if (Objects.isNull(entity)) {
                result = createAssetData(systemId, item);
            } else {
                result = modifyAssetData(entity, item, cover);
            }
            if (!result.isSuccess()) {
                throw new ImportAssetFailException(String.format("导入%s资产[%s]失败", AssetClassification.MANAGE_DOC.getName(), item.getName()));
            }
        }
    }

    private String getQuoteAssetId(AssetSDTO asset, List<AssetSDTO> importAssets, List<AssetDO> sourceAssets) {
        if (StringUtils.isBlank(asset.getQuoteAssetId())) {
            return StringUtils.EMPTY;
        }
        return Stream.of(StringUtils.split(asset.getQuoteAssetId(), BaseConstants.MULTI_SPLIT_CHAR))
                .filter(StringUtils::isNotBlank)
                .map(id -> this.findAsset(id, importAssets, sourceAssets))
                .filter(Objects::nonNull)
                .map(AssetDO::getId)
                .collect(Collectors.joining(BaseConstants.MULTI_SPLIT_CHAR));
    }

    private List<ImportConflictDTO> checkAssetData(@NonNull AssetSDTO importData,
                                                   List<AssetSDTO> importAssetData, List<AssetDO> sourceData) {
        AssetDO entity = findAsset(importData.getId(), importAssetData, sourceData);
        if (Objects.isNull(entity)) {
            return Collections.emptyList();
        }
        if(Objects.isNull(importData.getIsUse())){
            importData.setIsUse(true);
        }
        if(Objects.isNull(entity.getIsUse())){
            entity.setIsUse(true);;
        }
        List<ImportConflictDTO> conflictData = new ArrayList<>();
        if (!Objects.equals(importData.getIsUse(),entity.getIsUse())) {
            conflictData.add(ImportConflictDTO.builder()
                    .fieldName("是否测评")
                    .oldContent(entity.getIsUse() ? "是" : "否")
                    .newContent(importData.getIsUse() ? "是" : "否")
                    .build());
        }
        if (hasConflict(importData.getDesc(), entity.getDesc())) {
            conflictData.add(ImportConflictDTO.builder()
                    .fieldName("描述")
                    .oldContent(entity.getDesc())
                    .newContent(importData.getDesc())
                    .build());
        }
        conflictData.addAll(importData.getAttributes().stream()
                .map(a -> this.checkAssetData(a, entity.getAttributes()))
                .filter(Objects::nonNull)
                .toList());
        if (CollectionUtils.isNotEmpty(conflictData)) {
            conflictData.add(0, ImportConflictDTO.builder()
                    .fieldName(BaseConstants.IMPORT_CONFLICT_DATA_TAG)
                    .newContent(importData.getName())
                    .oldContent(entity.getName())
                    .build());
        }
        return conflictData;
    }

    private ImportConflictDTO checkAssetData(AssetAttributeSDTO importData, Set<AssetAttributeDO> sourceData) {
        AssetAttributeDO entity = sourceData.stream()
                .filter(st -> StringUtils.equals(st.getAttributeId(), importData.getAttributeId())
                        && StringUtils.equals(st.getFieldName(), importData.getFieldName()))
                .findFirst().orElse(null);
        if (Objects.nonNull(entity) && hasConflict(importData.getValue(), entity.getValue())) {
            return ImportConflictDTO.builder()
                    .fieldName(entity.getDisplayName())
                    .oldContent(entity.getValue())
                    .newContent(importData.getValue())
                    .build();
        } else {
            return null;
        }
    }

    private boolean hasConflict(Object sourceContent, Object importContent) {
        if (Objects.isNull(sourceContent) || Objects.isNull(importContent)) {
            return false;
        }
        return !StringUtils.equals(String.valueOf(sourceContent), String.valueOf(importContent));
    }


    private Result<String> createAssetData(String systemId, AssetSDTO importData) {
        AssetDO entity = AssetDO.builder()
                .systemId(systemId)
                .classId(importData.getClassId())
                .name(importData.getName())
                .isUse(importData.getIsUse())
                .desc(importData.getDesc())
                .quoteAssetId(importData.getQuoteAssetId())
                .keyDataAssetId(importData.getKeyDataAssetId())
                .build();
        entity.setCreateTime(LocalDateTime.now());
        Set<AssetAttributeDO> attributeDOS = importData.getAttributes()
                .stream()
                .map(a -> AssetAttributeDO.builder()
                        .attributeId(a.getAttributeId())
                        .fieldName(a.getFieldName())
                        .displayName(a.getDisplayName())
                        .value(a.getValue())
                        .sort(a.getSort())
                        .asset(entity)
                        .build())
                .peek(a -> a.setCreateTime(LocalDateTime.now()))
                .collect(Collectors.toSet());
        entity.setAttributes(attributeDOS);
        return Optional.of(entity)
                .map(this.assetRepository::save)
                .map(AssetDO::getId)
                .map(Result::success)
                .orElse(Result.failed(String.format("导入%s系统资产信息，创建失败", importData.getName())));
    }

    private Result<String> modifyAssetData(AssetDO sourceData, AssetSDTO importData, Boolean cover) {
        sourceData.setIsUse(CheckFieldUtils.applyContent(sourceData.getIsUse(), importData.getIsUse(), cover));
        sourceData.setDesc(CheckFieldUtils.applyContent(sourceData.getDesc(), importData.getDesc(), cover));
        sourceData.setQuoteAssetId(CheckFieldUtils.applyContent(sourceData.getQuoteAssetId(), importData.getQuoteAssetId(), cover));
        sourceData.setKeyDataAssetId(CheckFieldUtils.applyContent(sourceData.getKeyDataAssetId(), importData.getKeyDataAssetId(), cover));
        sourceData.getAttributes().forEach(attr -> importData.getAttributes()
                .stream()
                .filter(a -> StringUtils.equals(attr.getAttributeId(), a.getAttributeId()))
                .findFirst()
                .ifPresent(a -> {
                    attr.setValue(CheckFieldUtils.applyContent(attr.getValue(), a.getValue(), cover));
                    attr.setModifyTime(LocalDateTime.now());
                }));
        sourceData.setModifyTime(LocalDateTime.now());
        return Optional.of(sourceData)
                .map(this.assetRepository::save)
                .map(AssetDO::getId)
                .map(Result::success)
                .orElse(Result.failed(String.format("导入%s系统资产信息，修改失败", importData.getName())));
    }

}